/*  This file is part of YATracker.
    YATracker is free software: you can redistribute it and/or modify it under the terms
of the GNU Lesser General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
    YATracker is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License along with
YATracker. If not, see <http://www.gnu.org/licenses/>. */

#include <QtGui>
#include "qmapwidget.h"
#include "yatmapfile.h"

QMapWidget::QMapWidget()
{
    m_MapFile = NULL;
    m_ViewportX = m_ViewportY = 0;
    m_spotx = m_spoty = 0;
    m_targetx = m_targety = 0;
    m_targettimer.setInterval(120);

    QObject::connect(&m_targettimer, SIGNAL(timeout()), this, SLOT(targetTimerTick()));
}
QMapWidget::~QMapWidget()
{
    for (int i = 0; i < m_cache.size(); i++)
        delete m_cache[i].pimage;
}

void QMapWidget::setMapFile(YatMapFile *mapfile)
{
    m_MapFile = mapfile;

    if (m_MapFile == NULL)
    {
        m_ViewportX = m_ViewportY = 0;
    }
    else
    {
        //TODO: Get central point from the map file
        m_ViewportX = 21228 * 256 + 32;
        m_ViewportY = 10065 * 256 + 32;
    }
}

void QMapWidget::mousePressEvent(QMouseEvent * ev)
{
    m_MouseX = ev->x();
    m_MouseY = ev->y();
}
void QMapWidget::mouseReleaseEvent(QMouseEvent *)
{
}
void QMapWidget::mouseMoveEvent(QMouseEvent * ev)
{
    m_ViewportX += m_MouseX - ev->x();
    m_ViewportY += m_MouseY - ev->y();

    m_MouseX = ev->x();
    m_MouseY = ev->y();

    this->update();
}

QImage* QMapWidget::getTileImage(quint32 x, quint32 y)
{
    // Check tile cache for the tile [x,y]
    for (int i = 0; i < m_cache.size(); i++)
    {
        YatMapCacheItem& item = m_cache[i];
        if (item.tilex == x && item.tiley == y)
        {
            item.lastaccesstime = QDateTime::currentDateTime();
            return item.pimage;
        }
    }

    //qDebug() << "getTileImage() miss " << x << y;

    // Read tile [x,y] from the map file
    int tileDataSize;
    void* tileData = m_MapFile->getTile(x, y, &tileDataSize);
    if (tileData == NULL)
        return NULL;

    // Parse the image data
    QImage* image = new QImage();
    bool loaded = image->loadFromData((const uchar*)tileData, tileDataSize);
    free(tileData);
    if (!loaded)
    {
        delete image;
        return NULL;
    }

    const int maximumCacheSize = 250;
    if (m_cache.size() >= maximumCacheSize)
    {
        // Let's find an old cache item
        QDateTime mintime = m_cache[0].lastaccesstime;
        int minindex = 0;
        for (int i = 1; i < m_cache.size(); i++)
        {
            const YatMapCacheItem& item = m_cache.at(i);
            if (mintime > item.lastaccesstime)
            {
                mintime = item.lastaccesstime;
                minindex = i;
            }
        }
        // Remove the item
        delete m_cache[minindex].pimage;
        m_cache.remove(minindex);
    }

    // Prepare the new cache item
    YatMapCacheItem newitem;
    newitem.pimage = image;
    newitem.tilex = x;
    newitem.tiley = y;
    newitem.lastaccesstime = QDateTime::currentDateTime();
    m_cache.append(newitem);

    return image;
}

void QMapWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);

    //painter.fillRect(0, 0, this->width(), this->height(), Qt::white);

    if (m_MapFile != NULL)
    {
        // Draw tiles
        qint32 cymax = (this->m_ViewportY + this->height()) / 256;
        qint32 cxmax = (this->m_ViewportX + this->width()) / 256;
        qint32 x;
        qint32 y = -(qint32)(m_ViewportY % 256);
        qint32 cx;
        qint32 cy = this->m_ViewportY / 256;
        for (; cy <= cymax; cy++)
        {
            x = -(qint32)(m_ViewportX % 256);
            cx = this->m_ViewportX / 256;
            while (cx <= cxmax)
            {
                const QImage* pimage = this->getTileImage(cx, cy);
                if (pimage != NULL)
                {
                    // Draw the loaded tile
                    if (!pimage->isNull() && pimage->width() == 256 && pimage->height() == 256)
                    {
                        painter.drawImage(x, y, *pimage);
                    }
                }

                x += 256;  cx++;
            }

            y += 256;
        }

        // Draw tile bounds and numbers
        {
            char buffer[10];
            qint32 cx = this->m_ViewportX / 256;
            int x = -(int)(m_ViewportX % 256);
            int y = -(int)(m_ViewportY % 256);
            while (x < this->width())
            {
                painter.fillRect(x, 0, 1, this->height(), Qt::lightGray);

                // Draw tile X
                itoa((int)cx, buffer, 10);
                painter.drawText(x + 2, this->height() - 2, buffer);

                x += 256;  cx++;
            }
            qint32 cy = this->m_ViewportY / 256;
            while (y < this->height())
            {
                painter.fillRect(0, y, this->width(), 1, Qt::lightGray);

                // Draw tile Y
                itoa((int)cy, buffer, 10);
                painter.drawText(2, y + 12, buffer);

                y += 256;  cy++;
            }
        }
    }  // if (m_MapFile != NULL)

    // Draw the spot
    if (m_spotx != 0 || m_spoty != 0)
    {
        qint64 spotx = m_spotx - m_ViewportX;
        qint64 spoty = m_spoty - m_ViewportY;
        const qint64 spothalfsize = 32;
        const qint64 spotarchalfsize = 20;
        if (spotx > -spothalfsize && spotx < (qint64)this->width() + spothalfsize &&
            spoty > -spothalfsize && spoty < (qint64)this->height() + spothalfsize)
        {
            painter.fillRect(spotx - spothalfsize, spoty, spothalfsize*2, 1, Qt::red);
            painter.fillRect(spotx, spoty - spothalfsize, 1, spothalfsize*2, Qt::red);
            painter.setPen(Qt::red);
            painter.drawArc(spotx - spotarchalfsize, spoty - spotarchalfsize, spotarchalfsize*2, spotarchalfsize*2, 0, 5760);
        }
    }
}

void QMapWidget::convertLatLonToXY(double lat, double lon, qint64 *px, qint64 *py)
{
    int zoom = m_MapFile->getZoom();
    double xf = (lon + 180.0) / 360.0 * pow(2.0, zoom);
    double yf = (1.0 - log( tan(lat * M_PI/180.0) + 1.0 / cos(lat * M_PI/180.0)) / M_PI) / 2.0 * pow(2.0, zoom);
    *px = (qint64)floor(xf * 256);
    *py = (qint64)floor(yf * 256);
}

void QMapWidget::centerToSpot()
{
    if (m_spotx == 0 && m_spoty == 0)
        return;  // There is no spot
    this->centerTo(m_spotx, m_spoty);
}
void QMapWidget::centerTo(qint64 x, qint64 y)
{
    m_ViewportX = x - this->width() / 2;
    m_ViewportY = y - this->height() / 2;

    this->update();
}

void QMapWidget::setSpotPosition(double lat, double lon)
{
    //qDebug() << "setSpotPosition()" << lat << lon;
    qint64 spotxnew, spotynew;
    this->convertLatLonToXY(lat, lon, &spotxnew, &spotynew);
    if (m_spotx != spotxnew || m_spoty != spotynew)
    {
        m_spotx = spotxnew;
        m_spoty = spotynew;
        this->update();
    }
}

void QMapWidget::resetTargetPosition()
{
    m_targetx = m_targety = 0;
    m_targettimer.stop();
}
void QMapWidget::setTargetPosition(double lat, double lon)
{
    //qDebug() << "setTargetPosition()" << lat << lon;
    qint64 targetxnew, targetynew;
    this->convertLatLonToXY(lat, lon, &targetxnew, &targetynew);
    if (m_targetx != targetxnew || m_targety != targetynew)
    {
        m_targetx = targetxnew;
        m_targety = targetynew;
        // Turn on the timer
        if (!m_targettimer.isActive())
            m_targettimer.start();
    }
}
void QMapWidget::targetTimerTick()
{
    //qDebug() << "targetTimerTick()";
    if (m_targetx == 0 && m_targety == 0)
    {
        m_targettimer.stop();
        return;
    }

    // Calculate distance between the current center and the target
    qint64 centerx = m_ViewportX + this->width() / 2;
    qint64 centery = m_ViewportY + this->height() / 2;
    qint64 deltax = m_targetx - centerx;
    qint64 deltay = m_targety - centery;
    const qint64 targetTreshold = 16;
    if (abs(deltax) < targetTreshold && abs(deltay) < targetTreshold)
    {
        m_targetx = m_targety = 0;
        m_targettimer.stop();
        return;
    }

    qint64 speedx = (qint64)sqrt((double)abs(deltax));
    if (deltax < 0) speedx = -speedx;
    qint64 speedy = (qint64)sqrt((double)abs(deltay));
    if (deltay < 0) speedy = -speedy;

    // Move by (speedx,speedy)
    //TODO: Split to separate method, use scrolling
    m_ViewportX += speedx;
    m_ViewportY += speedy;
    this->update();

    m_targettimer.start();
}
